﻿using System;
using System.Collections.Generic;
using System.Configuration;
using BitCoinSharp;
using log4net;
using Taype.Trading.BTC.MtGoxAPIClient.JSONObjects;
using Taype.Trading.BTC.Utilities;

namespace Taype.Trading.BTC.MtGoxAPIClient
{

    /// <summary>
    /// This class implements v0 of the MtGox API as specified at https://en.bitcoin.it/wiki/MtGox/API#Methods_API_version_0
    /// </summary>
    public class v0
    {
        private readonly ILog logger;

        private string baseURL = "https://mtgox.com/api/";
        
        private string apiKey = ConfigurationManager.AppSettings["apikey"];

        private string apiSecret = ConfigurationManager.AppSettings["apiSecret"];

        /// <summary>
        /// Default constructor gets configuration from app.config
        /// </summary>
        public v0()
        {
            log4net.Config.XmlConfigurator.Configure();
            logger = LogManager.GetLogger(typeof(v0));
        }

        /// <summary>
        /// 0/data/getTrades.php 
        /// This allows retrieving all trades which happened in the last 24 hours. The returned data is cached and may not reflect latest activity. 
        /// </summary>
        /// <param name="sinceTradeID">Passing b tid in "since" allows retrieving all trades since that trade. The passed id is may not exist. Ie. to get all trades from the very beginning one would just call https://mtgox.com/code/data/getTrades.php?since=0 . since returns only 100 trades, and you can call the method again by passing the latest trade you have imported in since. </param>
        /// <returns>List of trades</returns>
        public List<MtGoxTrade> getTrades(string sinceTradeID)
        {
            try
            {
                string url = (this.baseURL) + "0/data/getTrades.php";
                string postData = "";
                if (sinceTradeID != "")
                    postData = "tid=" + sinceTradeID;
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("getTrades({0}) executed", sinceTradeID);
                return MtGoxTrade.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("getTrades({0}) failed! Exception: {1}", new object[]{sinceTradeID, ex.Message});
                return null;
            }
        }

        /// <summary>
        /// 0/data/getDepth.php 
        /// Get the current Market depth, e.g.:
        /// https://mtgox.com/api/0/data/getDepth.php?currency=PLN
        /// https://mtgox.com/api/0/data/getDepth.php?currency=AUD
        /// https://mtgox.com/api/0/data/getDepth.php?currency=USD 
        /// </summary>
        /// <param name="currency">MtGox currency symbol</param>
        /// <returns>List of bids and asks for the current time period (market depth)</returns>
        public MtGoxDepthInfo getDepth(MtGoxCurrencySymbol currency)
        {
            try
            {   
                string url = (this.baseURL) + "0/data/getDepth.php?currency=" + currency.ToString();
                string postData = "";
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("getDepth({0}) executed", currency.ToString());
                return MtGoxDepthInfo.getObjects(responseStr);

            }
            catch (Exception ex)
            {
                logger.InfoFormat("getDepth({0}) failed! Exception: {1}", new object[]{currency, ex.Message});
                return null;
            }
        }

        /// <summary>
        /// 0/getFunds.php 
        /// Get your current balance
        /// https://mtgox.com/api/0/getFunds.php
        /// getfunds is now deprecated since multi currency, please use info.php 
        /// </summary>
        /// <returns>Current account balance</returns>
        public double getFunds()
        {
            logger.InfoFormat("getFunds() executed", "");
            throw new NotImplementedException();
            // implementation below...
            //try
            //{
            //    string url = (this.baseURL) + "0/getFunds.php";
            //    string postData = "";
            //    string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
            //    // TODO: figure out b rational way to represent the info returned by this call
            //    // e.g. "{\"usds\":\"0.00523\",\"btcs\":\"44.956918\"}"
            //    logger.InfoFormat("getFunds() executed", "");
            //}
            //catch (Exception ex)
            //{
            //    logger.InfoFormat("getFunds() failed! Exception: {0}", ex.Message);
            //    return 0.0;
            //}
        }

        /// <summary>
        /// 0/buyBTC.php 
        /// Place an order to buy BTC
        /// https://mtgox.com/api/0/buyBTC.php
        /// POST data: amount=#&price=#&currency=PLN
        /// returns b list of your open orders
        /// you can omit the price to do b market order 
        /// </summary>
        /// <param name="amount">Lot size for purchase</param>
        /// <param name="price">Bid price, if ommitted simply place the order at market price</param>
        /// /// <param name="currency">Desired currency for purchase</param>
        /// <returns>List of open orders</returns>
        public List<MtGoxOrder> buyBTC(double amount, MtGoxCurrencySymbol currency, double price = 0.0)
        {
            try
            {
                string url = (this.baseURL) + "0/buyBTC.php";
                string postData;
                if (price == 0.0)
                    // this will submit an order at current market rate
                    postData = "amount=" + amount + "&currency=" + currency.ToString();
                else
                    // this will submit an order at the specified bid price
                    postData = "amount=" + amount + "&price=" + price + "&currency=" + currency.ToString();
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("buyBTC({0}, {1}, {2}) executed", new object[] { amount.ToString(), currency.ToString(), price.ToString() });
                return MtGoxOrder.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("buyBTC({0}, {1}, {2}) failed! Exception: {3}", new object[] {amount, currency.ToString(), price, ex.Message});
                return null;
            }
        }

        /// <summary>
        /// 0/sellBTC.php 
        /// Place an order to sell BTC
        /// https://mtgox.com/api/0/sellBTC.php
        /// POST data: &amount=#&price=#&currency=PLN
        /// returns b list of your open orders
        /// you can omit the price to do b market order 
        /// </summary>
        /// <param name="amount">Lot size for the sale</param>
        /// <param name="askPrice">Ask price for the sale, if ommitted simply sell at the current market price</param>
        /// <param name="currency">Desired currency for the sale</param>
        /// <returns>Open order</returns>
        public List<MtGoxOrder> sellBTC(double amount, MtGoxCurrencySymbol currency, double price = 0.0)
        {
            try
            {
                string url = (this.baseURL) + "0/sellBTC.php";
                string postData;
                if (price == 0.0)
                    // this will submit an order at current market rate
                    postData = "amount=" + amount + "&currency=" + currency.ToString();
                else
                    // this will submit an order at the specified bid price
                    postData = "amount=" + amount + "&price=" + price + "&currency=" + currency.ToString();
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("sellBTC({0}, {1}, {2}) executed", new object[] { amount.ToString(), currency.ToString(), price.ToString() });
                return MtGoxOrder.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("sellBTC({0}, {1}, {2}) failed! Exception: {3}", new object[] { amount, currency.ToString(), price, ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/getOrders.php 
        /// Fetch b list of your open Orders
        /// https://mtgox.com/api/0/getOrders.php
        /// oid: Order ID
        /// type: 1 for sell order or 2 for buy order
        /// status: 1 for active, 2 for not enough funds 
        /// <param name="oid">Unique identifier for order</param>
        /// <param name="status">status of order</param>
        /// <param name="type">Type of order</param>
        /// </summary>
        /// <returns>List of orders</returns>
        public List<MtGoxOrder> getOrders(MtGoxOrderType type, MtGoxOrderStatus status, int oid = 0)
        {
            try
            {
                // TODO: It looks like the type and status params HAVE to be passed in, need to refactor this into new functionality
                string url = (this.baseURL) + "0/getOrders.php";
                string postData = "";
                postData = "oid=" + oid;
                switch (type)
                {
                    case MtGoxOrderType.Buy:
                        postData += "&type=1";
                        break;
                    case MtGoxOrderType.Sell:
                        postData += "&type=2";
                        break;
                }
                switch (status)
                {
                    case MtGoxOrderStatus.Active:
                        postData += "&status=1";
                        break;
                    case MtGoxOrderStatus.NotSufficientFunds:
                        postData += "&status=2";
                        break;
                }
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("getOrders({0}, {1}, {2}) executed", new object[] { type.ToString(), status.ToString(), oid });
                // TODO: Complete implementation of JSON parsing
                return MtGoxOrder.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("getOrders({0}, {1}, {2}) failed! Exception: {3}", new object[] { type.ToString(), status.ToString(), oid, ex.Message });
                return null;
            }
        }

        ///// <summary>
        ///// Overload used to retrieve all orders regardless of type or status
        ///// </summary>
        ///// <returns></returns>
        //public List<MtGoxOrder> getOrders()
        //{
        //    // TODO: It looks like the type and status params HAVE to be passed in, need to refactor this into new functionality
        //    string url = (this.baseURL) + "0/getOrders.php";
        //    string[] postData = { "type=1&status=1", "type=2&status=1", "type=2&status=2", "type=1&status=2" };
        //    string responseStr;
        //    List<MtGoxOrder> results = new List<MtGoxOrder>();
        //    for (int i = 0; i < postData.Length; i++)
        //    {
        //        responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData[i]);
        //        List<MtGoxOrder> temp = MtGoxOrder.getObjects(responseStr);
        //        if (temp != null)
        //            results.AddRange(temp);
        //        logger.InfoFormat("getOrders() executed with postData = {0}", postData[i]);
        //    }
        //    return results;
        //}

        /// <summary>
        /// 0/cancelOrder.php 
        /// Cancel an order
        /// https://mtgox.com/api/0/cancelOrder.php
        /// POST data: oid=#&type=#
        /// oid: Order ID
        /// type: 1 for sell order or 2 for buy order 
        /// </summary>
        /// <returns></returns>
        public List<MtGoxOrder> cancelOrder(string oid, MtGoxOrderType type)
        {
            try
            {
                string url = (this.baseURL) + "0/cancelOrder.php";
                string postData;
                int t = 0;
                switch (type)
                {
                    case MtGoxOrderType.Buy:
                        t = 2;
                        break;
                    case MtGoxOrderType.Sell:
                        t = 1;
                        break;
                }
                postData = "oid=" + oid + "&type=" + t;
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("cancelOrder({0}, {1}) executed", new object[] { oid, type.ToString() });
                return MtGoxOrder.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("cancelOrder({0}) failed! Exception: {1}", new object[] { oid, ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/redeemCode.php 
        /// Used to redeem b mtgox coupon code
        /// https://mtgox.com/api/0/redeemCode.php
        /// call with b post parameter "code" containing the code to redeem 
        /// it will return an array with amount (float amount value of code), currency (3 letters, BTC or USD), reference (the transaction id), and status 
        /// <param name="code">Coupon code</param>
        /// </summary>
        public MtGoxCouponRedemptionResult redeemCode(string code)
        {
            try
            {
                string url = (this.baseURL) + "0/redeemCode.php";
                string postData = "code=" + code;
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("redeemCode({0}) executed", code);
                // TODO: implement JSON parsing
                return new MtGoxCouponRedemptionResult();
            }
            catch (Exception ex)
            {
                logger.InfoFormat("redeemCode({0}) failed! Exception: {1}", new object[] { code, ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/withdraw.php 
        /// withdraw / Send BTC
        /// https://mtgox.com/api/0/withdraw.php
        /// POST data: group1=BTC&btca=bitcoin_address_to_send_to&amount=#
        /// pass btca parameter to withdraw to b btc adress 
        /// pass group1 for b coupon : BTC2CODE or USD2CODE 
        /// pass group1=DWUSD for b dwolla withdraw 
        /// pass green=1 to use the new greenaddress feature ( see GreenAddress )
        /// return code and status if successful 
        /// To make b withdraw in another currency , use group1=USD2CODE and add b currency parameter ( example currency=EUR to get b mtgox EUR coupon )
        /// <param name="amount">amount to withdraw</param>
        /// <param name="currency">currency to make withdrawal in</param>
        /// <param name="btca">Destination address</param>
        /// <param name="green">Set to "1" to use greenaddress feature</param>
        /// <param name="group1">Withdrawal type</param>
        /// </summary>
        public object[] withdraw(MtGoxWithdrawalType group1, Address btca, double amount, MtGoxCurrencySymbol currency, string green = "")
        {
            try
            {
                string url = (this.baseURL) + "0/redeemCode.php";
                string postData = "btca=" + btca.ToString();
                postData += "&group1=" + group1.ToString();
                if (green != "")
                    postData += "&green=1";
                if (group1 == MtGoxWithdrawalType.USD2CODE)
                    postData += "&currency=" + currency.ToString();
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("withdraw({0}, {1}, {2}, {3}, {4}) executed", new object[] { group1.ToString(), btca.ToString(), amount.ToString(), currency.ToString(), green });
                // TODO: implement JSON parsing
                return null;
            }
            catch (Exception ex)
            {
                logger.InfoFormat("withdraw({0}, {1}, {2}, {3}) failed! Exception: {4}", new object[] { group1.ToString(), btca.ToString(), amount, currency.ToString(), green, ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/btcAddress.php
        /// get b bitcoin deposit adress for your account
        /// https://mtgox.com/api/0/btcAddress.php
        /// pass POST data "description" to add b description that will appear in your history when this BTC address receive b deposit 
        /// returns b bitcoin deposit address 
        /// </summary>
        /// <param name="description">Optional description linked to generated BTC address</param>
        /// <returns>Bitcoinsharp Address object</returns>
        public Address btcAddress(string description = "")
        {
            try
            {
                string url = (this.baseURL) + "0/btcAddress.php";
                string postData = "";
                if (description != "")
                    postData = "description=" + DateTime.UtcNow.Ticks.ToString();
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("btcAddress({0}) executed", description);
                return MtGoxBTCAddress.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("btcAddress({0}) failed! Exception: {1}", new object[] { description, ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/history_[CUR].csv
        /// Allows downloading your activity history for b given currency (BTC or USD for now).
        /// https://mtgox.com/api/0/history_BTC.csv
        /// https://mtgox.com/api/0/history_USD.csv 
        /// </summary>
        /// <param name="currency">currency to dump history for</param>
        /// <returns>String containing CSV file of account history</returns>
        public string history_CUR(MtGoxCurrencySymbol currency)
        {
            try
            {
                string url = (this.baseURL) + "0/history_" + currency.ToString() + ".csv";
                string postData = "";
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("history_CUR({0}) executed", currency.ToString());
                return responseStr;
            }
            catch (Exception ex)
            {
                logger.InfoFormat("history_{0} failed! Exception: {1}", new object[] { currency.ToString(), ex.Message });
                return null;
            }
        }

        /// <summary>
        /// 0/info.php
        /// https://mtgox.com/api/0/info.php
        /// returns info about your account, funds, fees, API privileges . . . 
        /// </summary>
        public MtGoxHistoryItem info()
        {
            try
            {
                string url = (this.baseURL) + "0/info.php";
                string postData = "";
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("info executed", "");
                return MtGoxHistoryItem.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("info() failed! Exception: {0}", ex.Message );
                return null;
            }
        }

        /// <summary>
        /// 0/ticker
        /// http://mtgox.com/api/0/data/ticker.php
        /// returns the current ticker
        /// </summary>
        public MtGoxTickerItem ticker()
        {
            try
            {
                string url = (this.baseURL) + "0/ticker.php";
                string postData = "";
                string responseStr = (new MtGoxNetTools()).DoAuthenticatedAPIPost(url, apiKey, apiSecret, postData);
                logger.InfoFormat("ticker executed", "");
                return MtGoxTickerItem.getObjects(responseStr);
            }
            catch (Exception ex)
            {
                logger.InfoFormat("ticker() failed! Exception: {0}", ex.Message);
                return null;
            }
        }

    }

}
